<?php
/**
 * Implements bot protocol
 * 
 * @link http://www.imified.com/developers/api#create
 */
class Imified {
    private $botkey;
    private $userkey;
    private $step;
    private $user;
    private $network;
    private $channel;
    private $message;
    private $aValues = array ();
    private $to;
    
    private static $verboseLevel = 4;
    
    /**
     * Get the bot key.
     * 
     * @return string
     */
    public function getBotkey() {
        return $this->botkey;
    }
    
    /**
     * Get the user key (unique ID of the user).
     * 
     * @return string
     */
    public function getUserkey() {
        return $this->userkey;
    }
    
    /**
     * Get the current step number.
     * 
     * @return int
     */
    public function getStep() {
        return $this->step;
    }
    
    /**
     * Get the user name that identifies him in the IM he is using (Ex.: JID for Jabber).
     * 
     * @return string
     */
    public function getUser() {
        return $this->user;
    }
    
    /**
     * Get the network the user used to contact the bot.
     * 
     * @return string
     */
    public function getNetwork() {
        return $this->network;
    }
    
    /**
     * Get the channel the user used to contact the bot.
     * 
     * @return string
     */
    public function getChannel() {
        return $this->channel;
    }
    
    /**
     * Get user's message.
     * 
     * @return string
     */
    public function getMessage() {
        return $this->message;
    }
    
    /**
     * Get the bot name in the IM user has used to contact the bot (Ex.: JID for Jabber)
     * 
     * @return string
     */
    public function getTo() {
        return $this->to;
    }
    
    /**
     * Get user's message for the specified step.
     * 
     * @param int $step
     * @return string
     */
    public function getValue($step) {
        if (array_key_exists($step, $this->aValues)) {
            return $this->aValues[$step];
        }
        else {
            return null;
        }
    }
    
    /**
     * Get all user's messages as the assoc: (step => message)
     * 
     * @return array
     */
    public function getValues() {
        return $this->aValues;
    }
    
    public static function verboseLevel($level = null) {
        if ($level === null) {
            return self::$verboseLevel;
        }
        else {
            self::$verboseLevel = $level;
        }
    }
    
    /**
     * Construct the object and read all parameters from POST request.
     */
    public function __construct() {
        
        // read all data from POST
        
        $this->botkey = $this->fromRequest("botkey");
        $this->userkey = $this->fromRequest("userkey");
        $this->step = $this->fromRequest("step");
        $this->user = $this->fromRequest("user");
        $this->network = $this->fromRequest("network");
        $this->channel = $this->fromRequest("channel");
        $this->message = $this->fromRequest("msg");
        $this->to = $this->fromRequest("to");
        
        $this->log("Request from: $this->user @ $this->network using $this->channel; Step: $this->step");
        
        // read values for previous steps
        
        foreach ($_REQUEST as $name => $value) {
            if (preg_match("`^value(\d+)$`", $name, $aMatches)) {
                $this->aValues[$aMatches[1]] = $value;
            }
        }
    }
    
    private function fromRequest($name, $default = null) {
        if (array_key_exists($name, $_REQUEST)) {
            return $_REQUEST[$name];
        }
        else {
            return $default;
        }
    }
    
    /**
     * Send answer to the user.
     * 
     * @param string $message
     */
    public function answer($message) {
        $this->sendBack($this->preparePlainMessage($message));
    }
    
    /**
     * Send error message to the user and also go one step back.
     * 
     * @param string $message
     */
    public function answerError($message) {
        $this->sendBack($this->preparePlainMessage($message) . "<error>");
    }
    
    /**
     * Go to the step specified and optionaly send message to the user.
     * 
     * @param int $step
     * @param string $message
     */
    public function answerGoto($step, $message = "") {
        $this->sendBack($this->preparePlainMessage($message) . "<goto=$step>");
    }
    
    /**
     * Send answer to the user and reset the bot (next time the step will be 1).
     * 
     * @param string $message
     */
    public function answerReset($message) {
        $this->sendBack($this->preparePlainMessage($message) . "<reset>");
    }
    
    protected function sendBack($text) {
        echo $text;
    }
    
    private function preparePlainMessage($message) {
        return nl2br(htmlspecialchars($message));
    }

    /**
     * Write message to file <full_path_to_here>/logs/<current_php_file_name>.php.log
     * 
     * @see Imified::verboseLevel()
     * @param string $message
     */
    protected function log($message, $level = 4) {
        if ($level <= self::$verboseLevel) {
            error_log(
                '[' . date("Y-m-d H:i:s") . '] ' . 
                    "[$level] " . 
                    '[' . self::getCallingMethod() . '] ' . 
                    $message . "\n",
                3, 
                dirname(__FILE__) . "/logs/" . basename(__FILE__) . ".log"
            );
        }
    }
    
    /**
     * Get the calling method name in the form: {class_name}::{method_name}.
     * 
     * @return string {class_name}::{method_name} or empty string
     */
    static private function getCallingMethod() {
        $result = "";
        
        $backtrace = debug_backtrace();
        if (isset($backtrace[2]) && isset($backtrace[2]["class"]) && isset($backtrace[2]["function"])) {
            $result = $backtrace[2]["class"] . "::" . $backtrace[2]["function"];
        }

        return $result;
    }
}

/**
 * Implements Imified.com API.
 * 
 * @link http://www.imified.com/developers/api#api
 */
class ImifiedApi {
    const API_URL = 'https://www.imified.com/api/bot/';
    const TIMEOUT = 120;
    
    private $accountUsername;
    private $accountPassword;
    private $botKey;
    
    /**
     * Construct the object and set connection parameters for the bot.
     * 
     * @param string $botKey
     * @param string $username
     * @param string $password
     */
    public function __construct($botKey, $username, $password) {
        $this->botKey = $botKey;
        $this->accountUsername = $username;
        $this->accountPassword = $password;
    }
    
    /**
     * Get the details for the specified user.
     * 
     * @param string $userKey
     */
    public function getUserDetails($userKey) {
        $oXml = $this->request(array (
            'botkey' => $this->botKey,
            'apimethod' => 'getuser',
            'userkey' => $userKey,
        ));
        
        return $this->convertUserToObject($oXml->user);
    }
    
    /**
     * Get all users known for the bot.
     * 
     * Optionally can be filtered by the network.
     * 
     * @param string $network valid values: Jabber, AIM, MSN, Yahoo, Gtalk, Twitter, SMS
     */
    public function getAllUsers($network = "") {
        $data = array (
            'botkey' => $this->botKey,
            'apimethod' => 'getallusers'
        );
        if ($network != "") {
            $data["Network"] = $network;
        }
        
        $oXml = $this->request($data);
        
        $a = (array) $oXml->users->children();
        $aUsers = $a["user"];
        if (!is_array($aUsers)) {
            $aUsers = array ($aUsers);
        }
        
        foreach ($aUsers as $i => $o) {
            $aUsers[$i] = $this->convertUserToObject($o);
        }
        
        return $aUsers;
    }
    
    private function convertUserToObject($user) {
        return (object)  array (
            "status"         => (string) $user->status,
            "extendedstatus" => (string) $user->extendedstatus,
            "userkey"        => (string) $user->userkey,
            "screenname"     => (string) $user->screenname,
            "user"           => (string) $user->user,
            "network"        => (string) $user->network,
            "created"        => preg_replace("`^\\{ts '([^']+)'\\}$`", "\\1", (string) $user->created),
            "lastcall"       => preg_replace("`^\\{ts '([^']+)'\\}$`", "\\1", (string) $user->lastcall),
            "lastonline"     => preg_replace("`^\\{ts '([^']+)'\\}$`", "\\1", (string) $user->lastonline)
        );
    }
    
    /**
     * Send message to the user.
     * 
     * User can be identified by user key or by pair: user, network
     * 
     * @param string $message
     * @param string $userKey
     * @param string $network valid values: Jabber, AIM, MSN, Yahoo, Gtalk, Twitter, SMS 
     */
    public function sendMessage($message, $userKey, $network = null) {
        $data = array (
            'botkey' => $this->botKey,
            'apimethod' => 'send',
            'msg' => nl2br(htmlspecialchars($message))
        );
        
        if ($network !== null) {
            $data['user'] = $userKey;
            $data['network'] = $network;
        }
        else {
            $data['userKey'] = $userKey;
        }
        
        $oXml = $this->request($data);
        
        return true;
    }
    
    /**
     * Update bot's status.
     * 
     * @param string $network valid values: Jabber, AIM, MSN, Yahoo, Gtalk, Twitter, SMS 
     * @param string $newStatus
     */
    public function updateStatus($network, $newStatus) {
        $data = array(
            'botkey' => $this->botKey,
            'apimethod' => 'updateStatus',
            'msg' => $newStatus,
            'network' => $network,
        );
        
        $oXml = $this->request($data);
        
        return true;
    }
    
    private function request($data) {
        $ch = curl_init();
        curl_setopt($ch, CURLOPT_URL, self::API_URL);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_USERPWD, $this->accountUsername . ":" . $this->accountPassword);
        curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
        curl_setopt($ch, CURLOPT_TIMEOUT, self::TIMEOUT);
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
        $xml = curl_exec($ch);
        if (curl_error($ch)) {
            throw new ImifiedApiException("cURL error: ". curl_error($ch));
        }
        curl_close($ch);
        
        $oXml = simplexml_load_string($xml);
        if (isset($oXml->err)) {
            $a = $oXml->err->attributes();
            throw new ImifiedApiException($a["msg"]);
        }
        
        return $oXml;
    }
}

class ImifiedApiException extends Exception {}
